// Copyright 2013 Petar Maymounkov
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package ripple

import (
	"bytes"
	"fmt"
	"io"
	"io/ioutil"
	"math/rand"
	"path"
	"strings"
	"time"

	"circuit/kit/iomisc"
	"circuit/kit/rh"
	"circuit/kit/rh/ns"
)

func buildChanHelp(name string, id uint64) string {
	return fmt.Sprintf(chanHelpFmt, path.Join("/", name), id)
}

const chanHelpFmt = `
  This is a channel ripple, named "%s", with QID ⟨Q%016x⟩.

  SYNOPSIS
  
  cat read             # Block and read data from the channel when available.
  echo hello >> write  # Write data to the channel.
  echo close >> close  # Close the channel.
  cat stat             # Read a description of the channel status.
  cat help             # Read this help screen.

` //

//
type Chan struct {
	q    rh.Q
	name string
	gtm  time.Time
	//
	r        iomisc.InterruptibleReader
	w        iomisc.InterruptibleWriter
	readFID  rh.FID
	writeFID rh.FID
	//
	closeFID rh.FID
	statFID  rh.FID
	helpFID  rh.FID
}

func NewChan(name string) (*Chan, rh.FID) {
	now := time.Now()
	c := &Chan{
		q: rh.Q{
			ID:  uint64(rand.Int63()),
			Ver: 1,
		},
		name: name,
		gtm:  now,
	}

	// read/write FIDs
	var ur, uw ns.UnregulatedFID
	uw, c.r = ns.NewPipeWriterFID("write")
	ur, c.w = ns.NewPipeReaderFID("read")

	c.writeFID = ns.RegulateShare(uw, ns.RegimeNeverClose)
	c.readFID = ns.RegulateShare(ur, ns.RegimeNeverClose)

	go func() {
		io.Copy(c.w, c.r)
		c.w.Close()
	}()

	// close FID
	c.closeFID = ns.RegulateFork(func() ns.UnregulatedFID {
		fid, r := ns.NewPipeWriterFID("close")
		go func() {
			body, err := ioutil.ReadAll(io.LimitReader(r, maxCommandLen))
			if err != nil {
				return
			}
			if strings.TrimSpace(string(body)) != "close" {
				return
			}
			c.Close()
		}()
		return fid
	})

	// stat FID
	c.statFID = ns.RegulateFork(func() ns.UnregulatedFID {
		nwritten, nread, wclosed, rclosed := c.Stat()
		body := fmt.Sprintf("BytesWritten: %d\nWriterEOF: %d\nBytesRead: %d\nReaderEOF=%d (BufferCap=0)\n", nwritten, b2b(wclosed), nread, b2b(rclosed))
		r := bytes.NewReader([]byte(body))
		//
		return ns.NewReaderFID("chan/stat", iomisc.ReaderNopCloser(r))
	})

	// help FID
	c.helpFID = NewHelp(buildChanHelp(name, uint64(rand.Int63())))

	return c, c.Walk()
}

func (c *Chan) Walk() rh.FID {
	return ns.NewFID("chan", &chanDir{c})
}

func (c *Chan) Read(p []byte) (n int, err error) {
	return c.r.Read(p)
}

func (c *Chan) Write(p []byte) (n int, err error) {
	return c.w.Write(p)
}

func (c *Chan) Close() (err error) {
	return c.r.Close()
}

func (c *Chan) Stat() (nwritten, nread int64, wclosed, rclosed bool) {
	nwritten, wclosed = c.r.Stat()
	nread, rclosed = c.w.Stat()
	return
}

//
type chanDir struct {
	c *Chan
}

func (cd *chanDir) Help() rh.FID {
	return cd.c.helpFID
}

func (cd *chanDir) Read() rh.FID {
	return cd.c.readFID
}

func (cd *chanDir) Write() rh.FID {
	return cd.c.writeFID
}

func (cd *chanDir) Close() rh.FID {
	return cd.c.closeFID
}

func (cd *chanDir) Stat() rh.FID {
	return cd.c.statFID
}

func b2b(b bool) byte {
	if b {
		return 1
	}
	return 0
}
